package com.ruge.core.log;

import lombok.extern.slf4j.Slf4j;
import org.springframework.core.Ordered;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.concurrent.atomic.AtomicLong;


/**
 * description: ReqRespLoggingFilter
 * create time at 2022/6/6 11:37
 *
 * @author alice.ruge
 * @since 0.0.3
 */

@Slf4j
public class ReqRespLoggingFilter implements Filter, Ordered {
    public static final String SENSITIVE_WORDS = "sensitive-words";
    public static final String LOG_BODY = "log_body";
    private static final String NOTIFICATION_PREFIX = "* ";
    private static final String REQUEST_PREFIX = "> ";
    private static final String RESPONSE_PREFIX = "< ";
    private static String uriSensitiveWordsRegex = "";
    private static String bodySensitiveWordsRegex = "";
    private ReqRespLoggingProperties reqRespLoggingProperties;
    private ThreadLocal<Long> requestId;
    private AtomicLong id;
    private boolean logHttpBody;

    public ReqRespLoggingFilter(ReqRespLoggingProperties reqRespLoggingProperties) {
        this(reqRespLoggingProperties.getSensitiveWords(), reqRespLoggingProperties, reqRespLoggingProperties.getLogHttpBody());
    }

    private ReqRespLoggingFilter(String sensitiveWords, ReqRespLoggingProperties reqRespLoggingProperties, boolean logHttpBody) {
        this.requestId = new ThreadLocal();
        this.id = new AtomicLong(1L);
        this.logHttpBody = logHttpBody;
        this.reqRespLoggingProperties = reqRespLoggingProperties;
        parseSensitiveWords(sensitiveWords);
    }

    @Override
    public int getOrder() {
        return -2147483648;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        if (log.isInfoEnabled()) {
            this.requestId.set(this.id.incrementAndGet());
            RequestWrapper requestWrapper = new RequestWrapper((HttpServletRequest) request);
            ResponseWrapper responseWrapper = new ResponseWrapper((HttpServletResponse) response);
            boolean needLog = this.needLog(requestWrapper);
            if (needLog) {
                this.logRequest(requestWrapper);
            }
            chain.doFilter(requestWrapper, responseWrapper);
            if (needLog) {
                this.logResponse(responseWrapper);
            }
        } else {
            chain.doFilter(request, response);
        }

    }

    @Override
    public void init(FilterConfig filterConfig) {
        this.logHttpBody = filterConfig.getInitParameter("log_body") == null || Boolean.parseBoolean(filterConfig.getInitParameter("log_body"));
        String words = filterConfig.getInitParameter("sensitive-words");
        parseSensitiveWords(words);
    }

    private void logRequest(RequestWrapper request) {
        try {
            StringBuilder b = new StringBuilder();
            this.printRequestLine(b, request);
            this.printRequestHeaders(b, request);
            if (this.logHttpBody) {
                this.printRequestBody(b, request);
            }

            log.info("{}\n", b);
        } catch (Exception var3) {
            log.error("{}", var3);
        }

    }

    private void logResponse(ResponseWrapper response) {
        try {
            StringBuilder b = new StringBuilder();
            this.printResponseLine(b, response);
            this.printResponseHeaders(b, response);
            if (this.logHttpBody) {
                this.printResponseBody(b, response);
            }

            log.info("{}\n", b);
        } catch (Exception var3) {
            log.error("{}", var3);
        }

    }

    private void printRequestLine(StringBuilder b, RequestWrapper request) {
        String requestUri = filterUriSensitiveWords(request.getRequestUri());
        this.prefixId(b).append("* ").append("Server in-bound request");
        this.prefixId(b).append("> ").append(request.getMethod()).append(" ").append(requestUri);
    }

    private void printResponseLine(StringBuilder b, ResponseWrapper response) {
        this.prefixId(b).append("* ").append("Server out-bound response");
        this.prefixId(b).append("< ").append(response.getStatus());
    }

    private void printRequestHeaders(StringBuilder b, RequestWrapper request) {

        Enumeration headerNameEnum = request.getHeaderNames();

        while (headerNameEnum.hasMoreElements()) {
            String headerName = (String) headerNameEnum.nextElement();
            String headerValue = request.getHeader(headerName);
            this.prefixId(b).append("> ").append(headerName).append(": ").append(headerValue);
        }

    }

    private void printResponseHeaders(StringBuilder b, ResponseWrapper response) {
        Collection<String> headerNames = response.getHeaderNames();
        Iterator var4 = headerNames.iterator();

        while (var4.hasNext()) {
            String headerName = (String) var4.next();
            this.prefixId(b).append("< ").append(headerName).append(": ").append(response.getHeader(headerName));
        }

    }

    private void printRequestBody(StringBuilder b, RequestWrapper request) {
        this.prefixId(b).append("> ").append('\n');
        String body = new String(request.getContentAsByteArray());
        body = filterBodySensitiveWords(body);
        b.append(body);
    }

    private void printResponseBody(StringBuilder b, ResponseWrapper response) {
        this.prefixId(b).append("< ").append('\n');
        String body;
        String keyword1 = "Content-Type";
        String keyword2 = "application/octet-stream";
        if (null != response.getHeader(keyword1) && keyword2.equals(response.getHeader(keyword1))) {
            body = "不显示Content-Type为application/octet-stream类型的body";
        } else {
            body = new String(response.toByteArray());
            body = filterBodySensitiveWords(body);
        }

        b.append(body);
    }

    private StringBuilder prefixId(StringBuilder b) {
        return b.append("\n").append(this.requestId.get()).append(" ");
    }

    protected boolean needLog(RequestWrapper request) {
        return !request.getRequestUri().matches(this.reqRespLoggingProperties.getBlackUrlRegex());
    }

    protected static void parseSensitiveWords(String words) {
        if (words != null && words.length() >= 1) {
            String[] ws = words.split(",");
            int var3 = ws.length;

            for (int var4 = 0; var4 < var3; ++var4) {
                String w = ws[var4];
                uriSensitiveWordsRegex = uriSensitiveWordsRegex + w + "[^&]*&?|";
                bodySensitiveWordsRegex = bodySensitiveWordsRegex + "\"?" + w + "[^,]*,|" + w + "[^&]*&?|";
            }

            uriSensitiveWordsRegex = uriSensitiveWordsRegex.length() > 0 ? uriSensitiveWordsRegex.substring(0, uriSensitiveWordsRegex.length() - 1) : uriSensitiveWordsRegex;
            bodySensitiveWordsRegex = bodySensitiveWordsRegex.length() > 0 ? bodySensitiveWordsRegex.substring(0, bodySensitiveWordsRegex.length() - 1) : bodySensitiveWordsRegex;
        }
    }

    protected static String filterUriSensitiveWords(String requestUri) {
        return "".equals(uriSensitiveWordsRegex) ? requestUri : requestUri.replaceAll(uriSensitiveWordsRegex, "");
    }

    protected static String filterBodySensitiveWords(String body) {
        if (!"".equals(bodySensitiveWordsRegex)) {
            body = body.replaceAll(bodySensitiveWordsRegex, "");
        }

        return body;
    }
}
